Visualizing Ocean Currents with Copernicus Marine Data & R

A Tutorial on Downloading and Mapping the Gulf Stream

Author

José Lastra M.

Published

November 23, 2025

Introduction

This tutorial demonstrates how to download ocean current data from the Copernicus Marine Service and create beautiful visualizations of the Gulf Stream. We’ll learn to:

  • Access Copernicus Marine data using the CopernicusMarine R package.
  • Process ocean current vectors (u and v components)
  • Calculate current speed.
  • Create multi-panel maps showing temporal changes

Setup

Install Required Packages

# Install pacman if needed
if (!require("pacman")) install.packages("pacman")

# Install all required packages
pacman::p_load(
  tidyverse,      # Data manipulation and visualization
  sf,             # Simple features for spatial data
  blosc,          # to handled compression of large data 
  rnaturalearth,  # World map data
  rnaturalearthdata,
  CopernicusMarine # Access Copernicus data
)

Load Libraries

pacman::p_load(
  tidyverse,      # Data manipulation and visualization
  sf,             # Simple features for spatial data
  blosc,          # to handled compression of large data 
  rnaturalearth,  # World map data
  rnaturalearthdata,
  CopernicusMarine # Access Copernicus data
)

Understanding the Data

Copernicus Marine Service

The Global Ocean Physics Analysis and Forecast provides:

  • Product ID: GLOBAL_ANALYSISFORECAST_PHY_001_024
  • Dataset cmems_mod_glo_phy-cur_anfc_0.083deg_P1D-m
  • Spatial Resolution: 0.083° (~9 km)
  • Variables: Ocean currents (u, v components), temperature, salinity, etc.
  • Temporal Coverage: Daily forecasts and historical data

Ocean Current Components

Ocean currents are described by two velocity components:

  • uo: Eastward velocity (u component) in m/s
  • vo: Northward velocity (v component) in m/s

The total speed is calculated as: \(\text{speed} = \sqrt{u^2 + v^2}\)

Data Download

Define Parameters

# Product information
product_id <- "GLOBAL_ANALYSISFORECAST_PHY_001_024"
dataset_id <- "cmems_mod_glo_phy-cur_anfc_0.083deg_P1D-m"

# Variables to download
vars <- c("uo", "vo")  # Current components

# Spatial extent (Gulf Stream region)
region <- c(-85, 20, -33, 68)  # lon_min, lat_min, lon_max, lat_max

# Depth level (surface)
depth_level <- c(0, -0.5)

# Time periods (monthly snapshots)
times <- seq.Date(
  ymd('2023-09-01'), 
  ymd('2023-12-01'), 
  by = 'month'
)

Download Data

This loop downloads data for each time period:

current_list <- lapply(times, function(tiempo) {
  
  # Download subset from Copernicus
  currents <- cms_download_subset(
    destination   = "file.nc",
    product       = product_id,
    layer         = dataset_id,
    variable      = vars,
    region        = region,
    timerange     = c(tiempo, tiempo),
    verticalrange = depth_level
  )
  
  # Convert to dataframe
  curr_df <- currents %>% 
    st_as_sf() %>% # from stars to sf
    mutate(across(where(~ inherits(.x, "units")), as.numeric)) %>% 
    mutate(
      lon = st_coordinates(st_centroid(.))[,1],
      lat = st_coordinates(st_centroid(.))[,2],
      speed = sqrt(uo^2 + vo^2),  # Calculate speed
      time_step = tiempo
    ) %>% 
    st_drop_geometry()
  
  return(curr_df)
})

# Combine all time periods
curr_df <- bind_rows(current_list)
NoteDownload Time

Downloading data can take several minutes depending on your internet connection, amount of data and the Copernicus server load.

Data Exploration

Let’s examine the structure of our data:

# View data structure
glimpse(curr_df)
Rows: 1,064,656
Columns: 6
$ uo        <dbl> -0.08716235, -0.12335874, -0.14933379, -0.15880683, -0.15646…
$ vo        <dbl> -0.06952941, -0.09073421, -0.11591692, -0.14538065, -0.16659…
$ lon       <dbl> -84.95833, -84.87499, -84.79166, -84.70833, -84.62499, -84.5…
$ lat       <dbl> 20.08333, 20.08333, 20.08333, 20.08333, 20.08333, 20.08333, …
$ speed     <dbl> 0.1114971, 0.1531342, 0.1890431, 0.2153024, 0.2285487, 0.232…
$ time_step <date> 2023-09-01, 2023-09-01, 2023-09-01, 2023-09-01, 2023-09-01,…
# Summary statistics
summary(curr_df$speed)
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
0.0001411 0.0891034 0.1520449 0.2131764 0.2610403 2.5532521 
# Check time periods
unique(curr_df$time_step)
[1] "2023-09-01" "2023-10-01" "2023-11-01" "2023-12-01"

Expected columns:

  • lon, lat: Geographic coordinates
  • uo, vo: Current velocity components (m/s)
  • speed: Calculated current speed (m/s)
  • time_step: Date of observation

Visualization

Prepare Map Components

# Sample data for arrow visualization (optional)
curr_sample <- curr_df %>% 
  sample_frac(0.005)  # Use 0.5% of points to avoid overcrowding

# Load world coastline
world <- ne_countries(scale = "small", returnclass = "sf")

Create the Map

g <- ggplot() +
  # Raster layer showing current speed
  geom_raster(
    data = curr_df, 
    aes(x = lon, y = lat, fill = speed)
  ) +
  # Coastline overlay
  geom_sf(
    data = world, 
    fill = 'black', 
    color = "white", 
    linewidth = 0.5
  ) +
  # Optional: Add current direction arrows
  # geom_segment(
  #   data = curr_sample,
  #   aes(x = lon, y = lat, 
  #       xend = lon + uo*10, 
  #       yend = lat + vo*10),
  #   arrow = arrow(length = unit(0.15, "cm")),
  #   linewidth = 0.5, 
  #   color = "black"
  # ) +
  # Color scale
  scale_fill_viridis_c(
    name = "Speed (m/s)", 
    option = "turbo"
  ) +
  # Set map bounds
  coord_sf(
    xlim = c(-85, -33), 
    ylim = c(20, 68), 
    expand = FALSE
  ) +
  # Create panels for each time period
  facet_wrap(~time_step, nrow = 1) +
  # Labels
  labs(
    title = "Gulf Stream Current Speed",
    subtitle = "September - December 2023",
    caption = "Data: Global Ocean Physics Analysis and Forecast, Copernicus Marine Service",
    x = "Longitude", 
    y = "Latitude"
  ) +
  # Theme
  theme(
    legend.position = 'bottom', 
    legend.title.position = 'top',
    legend.key.width = unit(2, "cm"), 
    text = element_text(colour = 'white', size = 15),
    axis.text = element_text(colour = 'white', size = 12),
    plot.background = element_rect(fill = "black"),
    legend.background = element_rect(fill = 'black')
  )
# Display
g

Save the Plot

ggsave(
  g, 
  filename = 'Gulf_Stream.png', 
  width = 15, 
  height = 8, 
  units = 'in', 
  dpi = 300
)

Interpretation

Understanding the Gulf Stream

The Gulf Stream is a powerful, warm ocean current in the North Atlantic that:

  • Originates in the Gulf of Mexico
  • Flows along the US East Coast
  • Affects weather patterns in North America and Europe
  • Shows seasonal and inter-annual variability

What to Look For

In the visualization, observe:

  1. Speed patterns: The core of the Gulf Stream (red/yellow) vs. slower surrounding waters
  2. Temporal changes: Monthly variations in position and intensity
  3. Eddies: Circular features that spin off from the main current
  4. Meandering: The Gulf Stream path is not straight but waves and curves

Resources